package com.xingbg.projectimpl.config.dubbo;

import com.xingbg.projectimpl.config.BeanOverride;
import lombok.SneakyThrows;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.config.spring.beans.factory.annotation.ServiceClassPostProcessor;
import org.apache.dubbo.config.spring.context.DubboBootstrapApplicationListener;
import org.apache.dubbo.config.spring.context.annotation.DubboClassPathBeanDefinitionScanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.CollectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import static com.alibaba.spring.util.BeanRegistrar.registerInfrastructureBean;
import static java.util.Arrays.asList;

/**
 * 重写dubbo扫描规则
 */
public class MyServiceClassPostProcessor extends ServiceClassPostProcessor {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private Environment environment;

    private ResourceLoader resourceLoader;

    /**
     * 需要排除的扫描类型
     */
    private final static List<Class<? extends Annotation>> serviceExcludeAnnotationTypes = asList(
            BeanOverride.class
    );

    public MyServiceClassPostProcessor(String... packagesToScan) {
        super(packagesToScan);
    }

    public MyServiceClassPostProcessor(Collection<String> packagesToScan) {
        super(packagesToScan);
    }

    public MyServiceClassPostProcessor(Set<String> packagesToScan) {
        super(packagesToScan);
    }

    /**
     * 获取父类配置的扫描服务注解
     * @return
     */
    @SneakyThrows
    private List<Class<? extends Annotation>> getServiceAnnotationTypes() {
        Field serviceAnnotationTypes = this.getClass().getSuperclass().getDeclaredField("serviceAnnotationTypes");
        serviceAnnotationTypes.setAccessible(true);
        return (List<Class<? extends Annotation>>)serviceAnnotationTypes.get(null);
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        // @since 2.7.5
        registerInfrastructureBean(registry, DubboBootstrapApplicationListener.BEAN_NAME, DubboBootstrapApplicationListener.class);

        Set<String> resolvedPackagesToScan = (Set<String>)this.invokeSuperMethod("resolvePackagesToScan",packagesToScan); //resolvePackagesToScan(packagesToScan);

        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
            registerServiceBeans(resolvedPackagesToScan, registry);
        } else {
            if (logger.isWarnEnabled()) {
                logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
            }
        }
    }

    /**
     * Registers Beans whose classes was annotated {@link Service}
     *
     * @param packagesToScan The base packages to scan
     * @param registry       {@link BeanDefinitionRegistry}
     */
    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

        DubboClassPathBeanDefinitionScanner scanner =
                new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);

        BeanNameGenerator beanNameGenerator = (BeanNameGenerator)this.invokeSuperMethod("resolveBeanNameGenerator",registry);
                //resolveBeanNameGenerator(registry);

        scanner.setBeanNameGenerator(beanNameGenerator);

        // refactor @since 2.7.7
        getServiceAnnotationTypes().forEach(annotationType -> {
            scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
        });
        // 修改父类方法，增加排除扫描注解
        serviceExcludeAnnotationTypes.forEach(annotationType ->{
            scanner.addExcludeFilter(new AnnotationTypeFilter(annotationType));
        });

        for (String packageToScan : packagesToScan) {

            // Registers @Service Bean first
            scanner.scan(packageToScan);

            // Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not.
            Set<BeanDefinitionHolder> beanDefinitionHolders =
                    (Set<BeanDefinitionHolder>)this.invokeSuperMethod("findServiceBeanDefinitionHolders",scanner, packageToScan, registry, beanNameGenerator);
                    //findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);

            if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {

                for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                    this.invokeSuperMethod("registerServiceBean",beanDefinitionHolder,registry,scanner);
                    //registerServiceBean(beanDefinitionHolder, registry, scanner);
                }

                if (logger.isInfoEnabled()) {
                    logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " +
                            beanDefinitionHolders +
                            " } were scanned under package[" + packageToScan + "]");
                }

            } else {

                if (logger.isWarnEnabled()) {
                    logger.warn("No Spring Bean annotating Dubbo's @Service was found under package["
                            + packageToScan + "]");
                }

            }

        }

    }

    /**
     * 调用父类方法
     * @param methodName
     * @param parameters
     * @return
     */
    @SneakyThrows
    private Object invokeSuperMethod(String methodName,Object... parameters) {

        Method declaredMethod = null;
        if (parameters==null || parameters.length==0) {
            declaredMethod = this.getClass().getSuperclass().getDeclaredMethod(methodName);
        } else {
            Class[] parameterClasses = new Class[parameters.length];
            for(int i=0;i<parameters.length;i++) {
                parameterClasses[i]=parameters[i].getClass();
            }
            Method[] declaredMethods = this.getClass().getSuperclass().getDeclaredMethods();
            for (Method method : declaredMethods) {
                if (!methodName.equals(method.getName())) {
                    continue;
                }
                Class<?>[] methodParaTypes = method.getParameterTypes();
                if (methodParaTypes.length!=parameterClasses.length) {
                    continue;
                }
                boolean parameterMatch = true;
                for (Class<?> methodParaType : methodParaTypes) {
                    if (!Arrays.stream(parameterClasses).anyMatch(p->methodParaType.isAssignableFrom(p))) {
                        parameterMatch = false;
                        break;
                    }
                }

                if (!parameterMatch) {
                    continue;
                }
                declaredMethod = method;
                break;
            }

        }

        declaredMethod.setAccessible(true);

        return declaredMethod.invoke(this,parameters);

    }


    @Override
    public void setEnvironment(Environment environment) {
        super.setEnvironment(environment);
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        super.setResourceLoader(resourceLoader);
        this.resourceLoader = resourceLoader;
    }
}
